﻿namespace DataDuctus.MongoDb.Serialization.Tests
{
	using System.Linq;

	using DataDuctus.MongoDb.Serialization.Tests.Model;
	using DataDuctus.MongoDb.Serialization.Tests.Utils;

	using Microsoft.VisualStudio.TestTools.UnitTesting;

	using MongoDB.Bson.Serialization;
	using MongoDB.Driver.Builders;
	
	/// <summary>
	/// Summary description for MongoDbSerializationServerTests
	/// </summary>
	[TestClass]
	public class MongoDbSerializationServerTests : MongoServerBasedTests
	{
		#region Primitive Tests
		[TestMethod]
		public void ReadWritePrimitivePropertyClass()
		{
			if (!BsonClassMap.IsClassMapRegistered(typeof(PrimitiveTypesPropertiesClass)))
			{
				new DataContractMapper<PrimitiveTypesPropertiesClass>().Register();
			}

			var coll = this.TestDatabase.GetCollection<PrimitiveTypesPropertiesClass>("testColl");

			var cls = new PrimitiveTypesPropertiesClass
			{
				ByteProperty = byte.MinValue,
				CharProperty = char.MinValue,
				DoubleProperty = double.MinValue,
				FloatProperty = float.MinValue,
				IntProperty = int.MinValue,
				LongProperty = long.MinValue,
				ShortProperty = short.MinValue,
				StringProperty = "Hello world",
				UIntProperty = uint.MinValue,
				ULongProperty = ulong.MinValue,
				UShortProperty = ushort.MinValue
			};

			coll.Save(cls);

			var obj = coll.FindOneByIdAs<PrimitiveTypesPropertiesClass>(cls.Id);

			CompareAndAssert(cls, obj, new CompareObjects());
		}

		[TestMethod]
		public void ReadWritePrimitiveFieldClass()
		{
			if (!BsonClassMap.IsClassMapRegistered(typeof(PrimitiveTypesFieldsClass)))
			{
				new DataContractMapper<PrimitiveTypesFieldsClass>().Register();
			}

			var coll = this.TestDatabase.GetCollection<PrimitiveTypesFieldsClass>("testColl");

			var cls = new PrimitiveTypesFieldsClass(
				"Hello world",
				int.MaxValue,
				long.MaxValue,
				double.MaxValue,
				float.MaxValue,
				short.MaxValue,
				uint.MaxValue,
				ulong.MaxValue,
				ushort.MaxValue,
				byte.MaxValue,
				char.MaxValue);

			coll.Save(cls);

			var obj = coll.FindOneByIdAs<PrimitiveTypesFieldsClass>(cls.Id);

			CompareAndAssert(cls, obj, new CompareObjects { ComparePrivateFields = true });
		}
		#endregion // Primitive Tests

		#region Single Inheritance
		[TestMethod]
		public void ReadWriteSingleInheritanceClasses()
		{
			if (!BsonClassMap.IsClassMapRegistered(typeof(OneInheritanceA)))
			{
				new DataContractMapper<OneInheritanceA>().Register(); // Should register OneInhertianceA, OneInhertianceB
			}

			var coll = this.TestDatabase.GetCollection<OneInheritanceA>("OneInheritanceA");

			coll.RemoveAll(); // Clear everything

			var clsA = new OneInheritanceA { BaseName = "This is the base" };

			coll.Save(clsA);

			var obj = coll.FindOneByIdAs<OneInheritanceA>(clsA.Id);

			CompareAndAssert(clsA, obj, new CompareObjects());

			var clsB = new OneInheritanceB { BaseName = "This is the base", SubClassName = "This is the subname" };

			coll.Save(clsB);

			var objB = coll.FindOneByIdAs<OneInheritanceB>(clsB.Id);

			CompareAndAssert(clsB, objB, new CompareObjects());

			// TODO: this does not work...
			//var items = (from item in coll.AsQueryable<OneInheritanceA>() where item.BaseName == "This is the base" select item).ToArray();

			var classAItems = coll.FindAs<OneInheritanceA>(Query.EQ("BaseName", "This is the base")).ToArray();

			Assert.AreEqual(2, classAItems.Length);
			Assert.AreEqual(1, classAItems.OfType<OneInheritanceB>().Count());
		}
		#endregion // Single Inheritance

		#region Single Level Composition
		[TestMethod]
		public void ReadWriteSingleLevelCompositionClass()
		{
			if (!BsonClassMap.IsClassMapRegistered(typeof(SingleLevelCompositionMain)))
			{
				new DataContractMapper<SingleLevelCompositionMain>().Register();
			}

			var coll = this.TestDatabase.GetCollection<SingleLevelCompositionMain>("SingleLevelCompositionMain");

			coll.RemoveAll(); // Clear everything

			var cls = new SingleLevelCompositionMain
				{
					Composite =
						new SingleLevelCompositionComposite { StringProperty = "Property in first level composite (from main counted)" }
				};

			coll.Save(cls);

			var clsLoaded = coll.FindOneByIdAs<SingleLevelCompositionMain>(cls.Id);

			CompareAndAssert(cls, clsLoaded, new CompareObjects());
		}
		#endregion // Single Level Composition

		#region Single Level Composition Single Inheritance
		[TestMethod]
		public void ReadWriteSingleLevelCompositionSingleInheritance()
		{
			if (!BsonClassMap.IsClassMapRegistered(typeof(SingleLevelCompositionOneInheritanceMain)))
			{
				new DataContractMapper<SingleLevelCompositionOneInheritanceMain>().Register();
			}

			var coll = this.TestDatabase.GetCollection<OneInheritanceA>("SingleLevelCompositionOneInheritanceMain");

			coll.RemoveAll(); // Clear everything

			var clsA = new SingleLevelCompositionOneInheritanceMain
				{ Composite = new SingleLevelCompositionOneInheritanceA { BaseName = "Base only as composite" } };

			var clsB = new SingleLevelCompositionOneInheritanceMain
				{
					Composite =
						new SingleLevelCompositionOneInheritanceB { BaseName = "Base in composite", SubClassName = "Sub in composite" }
				};

			coll.Save(clsA);

			var objA = coll.FindOneByIdAs<SingleLevelCompositionOneInheritanceMain>(clsA.Id);

			CompareAndAssert(clsA, objA, new CompareObjects());

			coll.Save(clsB);

			var objB = coll.FindOneByIdAs<SingleLevelCompositionOneInheritanceMain>(clsB.Id);

			CompareAndAssert(clsB, objB, new CompareObjects());
		}
		#endregion // Single Level Composition Single Inheritance

		#region Multi Level Composition
		[TestMethod]
		public void ReadWriteMapMultiLevelComposition()
		{
			if (!BsonClassMap.IsClassMapRegistered(typeof(MulitLevelCompositionMain)))
			{
				new DataContractMapper<MulitLevelCompositionMain>().Register();
			}

			var coll = this.TestDatabase.GetCollection<MulitLevelCompositionMain>("MulitLevelCompositionMain");

			coll.RemoveAll(); // Clear everything

			var cls = new MulitLevelCompositionMain
				{
					Composite =
						new MulitLevelCompositionCompositeA
							{
								StringProperty = "First composite level",
								Composite =
									new MulitLevelCompositionCompositeB
										{
											StringProperty = "Second composite level",
											Composite =
												new MulitLevelCompositionCompositeC
													{
														StringProperty = "Third composite level",
														Composite =
															new MulitLevelCompositionCompositeD
																{ LastStringProperty = "Last (Forth composite level) property down the chain" }
													}
										}
							}
				};

			coll.Save(cls);

			var obj = coll.FindOneByIdAs<MulitLevelCompositionMain>(cls.Id);

			CompareAndAssert(cls, obj, new CompareObjects());
		}
		#endregion // Multi Level Composition

		#region KnownType for non inherited class gets misinterpreted (Issiue#764)
		[TestMethod]
		public void Issiue764()
		{
			if (!BsonClassMap.IsClassMapRegistered(typeof(TypeContainingAnKnownTypeOfUndeterminablePropertyType)))
			{
				// Should register TypeContainingAnKnownTypeOfUndeterminablePropertyType, TypeThatResidesInUnterminablePropertyType
				// where TypeThatResidesInUnterminablePropertyType should be registered as a "regular" classmap not a known type to
				// TypeContainingAnKnownTypeOfUndeterminablePropertyType.
				new DataContractMapper<TypeContainingAnKnownTypeOfUndeterminablePropertyType>().Register();
			}

			var coll = this.TestDatabase.GetCollection<TypeContainingAnKnownTypeOfUndeterminablePropertyType>("TypeContainingAnKnownTypeOfUndeterminablePropertyType");

			coll.RemoveAll(); // Clear everything

			var cls = new TypeContainingAnKnownTypeOfUndeterminablePropertyType
				{ DataContractMappedType = new TypeThatResidesInUnterminablePropertyType { HelloWorldProperty = "Hello world" } };

			coll.Save(cls);

			var obj = coll.FindOneByIdAs<TypeContainingAnKnownTypeOfUndeterminablePropertyType>(cls.Id);

			CompareAndAssert(cls, obj, new CompareObjects());
		}
		#endregion // KnownType for non inherited class gets misinterpreted (Issiue#764)

		#region Bastype Abstract Property Subclass With Decorated Override (Issiue#766)
		[TestMethod]
		public void Issiue766()
		{
			if (!BsonClassMap.IsClassMapRegistered(typeof(BaseTypeAbstractProperty)))
			{
				new DataContractMapper<BaseTypeAbstractProperty>().Register(); // Should register BaseTypeAbstractProperty, SubclassDecoratedOverride
			}

			var coll = this.TestDatabase.GetCollection<BaseTypeAbstractProperty>("BaseTypeAbstractProperty");

			coll.RemoveAll(); // Clear everything

			var cls = new SubclassDecoratedOverride { AbstractPropertyDecorated = "Hello world" };

			coll.Save(cls);

			var obj = coll.FindOneByIdAs<BaseTypeAbstractProperty>(cls.Id);

			CompareAndAssert(cls, obj, new CompareObjects());
		}
		#endregion Bastype Abstract Property Subclass With Decorated Override (Issiue#766)

		#region Private Helpers
		private void CompareAndAssert(object object1, object object2, CompareObjects comparer)
		{
			Assert.IsTrue(comparer.Compare(object1, object2), comparer.DifferencesString);
		}
		#endregion // Private Helpers
	}
}
